package glpk

import assignment.Skill;
import assignment.PersonController;
import assignment.Person;
import assignment.Task;
import assignment.Period;
import app.*;

class GlpkModel {

	String personSet = Constants.personSetName;
	String taskSet = Constants.taskSetName;
	String skillSet = Constants.skillSetName;
	String periodSet = Constants.periodSetName;
	
	
	String personIndex = Constants.personSetIndex;
	String taskIndex = Constants.taskSetIndex;
	String skillIndex = Constants.skillSetIndex;
	String periodIndex = Constants.periodSetIndex;
	


	//FIXME use genericity?
	public String getPersonList(String quote,String separator) {
		String result='';
		String content = '';
		Person.list().each{ it -> content+= "$quote${it.toString()}$quote$separator" };
		try{
			result += content.substring(0, content.length()-1);
		}
		catch(Exception e) {
			println(e.getMessage());
		}
		return result;
	}

	//FIXME use genericity?
	public String getTaskList(String quote,String separator) {
		String result='';
		String content = '';
		Task.list().each{ it -> content+= "$quote${it.toString()}$quote$separator" };
		try{
			result += content.substring(0, content.length()-1);
		}
		catch(Exception e) {
			println(e.getMessage());
		}
		return result;
	}

	//FIXME use genericity?
	public String getSkillList(String quote,String separator) {
		String result='';
		String content = '';
		Skill.list().each{ it -> content+= "$quote${it.toString()}$quote$separator" };
		try{
			result += content.substring(0, content.length()-1);
		}
		catch(Exception e) {
			println(e.getMessage());
		}
		return result;
	}
	
	//FIXME use genericity?
	public String getPeriodList(String quote,String separator) {
		String result='';
		String content = '';
		Period.list().each{ it -> content+= "$quote${it.toString()}$quote$separator" };
		try{
			result += content.substring(0, content.length()-1);
		}
		catch(Exception e) {
			println(e.getMessage());
		}
		return result;
	}
	

	public String toString() {
		return getModelHeader() + Constants.lineSeparator +
		getModelParameters() + Constants.lineSeparator +
		getModelVariables() + Constants.lineSeparator +
		getModelObjective() + Constants.lineSeparator +
		getModelConstraints() + Constants.lineSeparator

		//System.out.println(getPersonList());
		;
	}


	//commentaire d'en-tête
	public String getModelHeader() {
		String st = """
		${Constants.commentOpen} ASSIGN, Assignment Problem ${Constants.commentClose}
		${Constants.commentOpen} The assignment problem is one of the fundamental combinatorial optimization problems.
		In its most general form, the problem is as follows:
		There are a number of agents and a number of tasks. Any agent can be assigned to perform any task, incurring some cost that may vary depending on the agent-task assignment. It is required to perform all tasks by assigning exactly one agent to each task in such a way that the total cost of the assignment is minimized.
		(From Wikipedia, the free encyclopedia.) ${Constants.commentClose}"""+ Constants.lineSeparator;

		return st;
	}

	//declaration parametres
	public String getModelParameters() {
		String st = '';

		st+="""set ${personSet}:= {${this.getPersonList('',Constants.coma)}};
		${Constants.commentOpen} set of agents ${Constants.commentClose}""" + Constants.lineSeparator;

		st+="""set ${taskSet}:= {${this.getTaskList('',Constants.coma)}};
		${Constants.commentOpen} set of tasks ${Constants.commentClose}""" + Constants.lineSeparator;

		st+="""set ${skillSet}:= {${this.getSkillList('',Constants.coma)}};
		${Constants.commentOpen} set of skill ${Constants.commentClose}""" + Constants.lineSeparator;
	
		st+="""set ${periodSet}:= {${this.getPeriodList('',Constants.coma)}};
		${Constants.commentOpen} set of period ${Constants.commentClose}""" + Constants.lineSeparator;

		st+="""param personSkills{${personIndex} in ${personSet}, ${skillIndex} in ${skillSet}}, >= 0;
		${Constants.commentOpen} skills of agent ${personSet} ${Constants.commentClose}""" + Constants.lineSeparator

		st+="""param taskSkills{${taskIndex} in ${taskSet}, ${skillIndex} in ${skillSet}}, >= 0;
		${Constants.commentOpen} skills of task ${taskSet} ${Constants.commentClose}""" + Constants.lineSeparator
	
		st+="""param personPeriods{${personIndex} in ${personSet}, ${periodIndex} in ${periodSet}}, >= 0;
		${Constants.commentOpen} availability of agent ${personSet} ${Constants.commentClose}""" + Constants.lineSeparator

		st+="""param taskPeriods{${taskIndex} in ${taskSet}, ${periodIndex} in ${periodSet}}, >= 0;
		${Constants.commentOpen} availability of task ${taskSet} ${Constants.commentClose}""" + Constants.lineSeparator


		//TODO use preference as cost in the model (written in the glpk file but no value yet)
		/*
		 st+="""param c{${personIndex} in ${personSet}, ${taskIndex} in ${taskSet}}, >= 0;
		 ${Constants.commentOpen} cost(preference) of allocating task ${taskSet} to agent ${personSet} ${Constants.commentClose}""" + Constants.lineSeparator
		 */
		return st;
	}


	//declaration variable
	public String getModelVariables() {

		String st = """var ${Constants.variableName}{${personIndex} in ${personSet}, ${taskIndex} in ${taskSet}, ${periodIndex} in ${periodSet}}, >= 0;
		${Constants.commentOpen} ${Constants.variableName}[${personIndex},${taskIndex},${periodIndex}] = 1 means task ${taskIndex} is assigned to agent ${personIndex} at a given time ${periodIndex}. Note that variables ${Constants.variableName}[${personIndex},${taskIndex},${periodIndex}] are binary, however, there is no need to declare them so due to the totally unimodular constraint matrix ${Constants.commentClose}""" + Constants.lineSeparator

		return st;
	}

	//fonction objective
	public String getModelObjective() {
		String st = """maximize obj: sum{${personIndex} in ${personSet}, ${taskIndex} in ${taskSet}, ${periodIndex} in ${periodSet}} ${Constants.variableName}[${personIndex},${taskIndex},${periodIndex}];
		${Constants.commentOpen} the objective is to find a max assignment ${Constants.commentClose}"""+ Constants.lineSeparator

		return st;
	}

	//contraintes
	public String getModelConstraints() {
		String st='';
		st += getConstraintPersons() + 
			getConstraintTasks() +
			getConstraintSkills() +
			getConstraintPeriods();
		
		return st;
	}
	
	//contraintes
	public String getConstraintPersons() {
		String st='';
		st += """
		s.t. constraintPersons{${personIndex} in ${personSet}}: sum{${taskIndex} in ${taskSet}, ${periodIndex} in ${periodSet}} ${Constants.variableName}[${personIndex},${taskIndex},${periodIndex}] <= 1;
		${Constants.commentOpen} each agent can perform at most one task at given time ${Constants.commentClose}""" + Constants.lineSeparator

		return st;
	}
	
	//contraintes
	public String getConstraintTasks() {
		String st='';

		st+= """
		s.t. constraintTasks{${taskIndex} in ${taskSet}}: sum{${personIndex} in ${personSet}, ${periodIndex} in ${periodSet}} ${Constants.variableName}[${personIndex},${taskIndex},${periodIndex}] <= 1;
		${Constants.commentOpen} each task can be assigned to at most one agent  at given time ${Constants.commentClose}""" + Constants.lineSeparator

		return st;
	}
	
	//contraintes
	public String getConstraintSkills() {
		String st='';

		st+= """
		s.t. constraintSkills{${personIndex} in ${personSet}, ${taskIndex} in ${taskSet}, ${skillIndex} in ${skillSet}, ${periodIndex} in ${periodSet}}: taskSkills[${taskIndex},${skillIndex}] * ${Constants.variableName}[${personIndex},${taskIndex},${periodIndex}] <= personSkills[${personIndex},${skillIndex}];
		${Constants.commentOpen} each task required skills must match to assigned agent skills ${Constants.commentClose}""" + Constants.lineSeparator

		return st;
	}
	
	//contraintes
	public String getConstraintPeriods() {
		String st='';

		st+= """
		s.t. constraintPeriods{${personIndex} in ${personSet}, ${taskIndex} in ${taskSet}, ${periodIndex} in ${periodSet}}: taskPeriods[${taskIndex},${periodIndex}] * ${Constants.variableName}[${personIndex},${taskIndex},${periodIndex}] <= personPeriods[${personIndex},${periodIndex}];
		${Constants.commentOpen} each task required period must match to assigned agent availability ${Constants.commentClose}""" + Constants.lineSeparator

		return st;
	}

}
